/* ds1820.c

Copyright (C) Alex S. (mailto: md5sum@alexsp.ru)

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details
http://www.gnu.org/licenses/gpl-2.0.html.

You should have received a copy of the GNU General Public License
along with this program in the file gpl-2.0.txt;
if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "ds1820.h"

#define INIT_max_num 1 //Кол-во попыток инициализировать датчик

/*DDRx*/
#define _DDRx *(__DDRx_p)
/*PORTx*/
#define _PORTx *(__DDRx_p+1)
/*PINx*/
#define _PINx *(__DDRx_p-1)

/*Используем следующие команды для работы с ds1820*/
#define DS1820_SKIP_ROM 0xCC
#define DS1820_CONVERT_T 0x44
#define DS1820_READ_SCRATCHPAD 0xBE
#define DS1820_READ_ROM 0x33

/*--------------------------------------------------*/
/*Посылка init на указанный порт/пин*/
static ds1820_ret_status ds1820_init(volatile uint8_t *__DDRx_p,const uint8_t _MASK) {
	uint8_t tmp;
	int err=1000;

	_DDRx&=~_MASK; //Порт на чтение

#if USE_Watchdog==1
	wdt_reset(); //Сброс собаки
#endif

	/*Пока на линии 0 - ждем,но не более 10 милисек.*/
	while ( (_PINx&_MASK)==0 ) {
		if (err==0) return SHORT;
		err--;
		_delay_us(10);
	}

	/* INIT_max_num попыток инициализировать датчик*/
	for (err=INIT_max_num;err>0;err--) {
		_PORTx &= ~_MASK; /*Порт в 0*/
		_DDRx  |= _MASK; /*Порт на вывод*/

		_delay_us(500); /*Ждем 500 мксек*/

		_DDRx &= ~_MASK; /*Порт на ввод*/

		for (tmp=0;tmp<7;tmp++) { //"waits 15-60 μs" тут ждем до 60 проверяя каждые 10
			_delay_us(10);
			if ((_PINx&_MASK)==0) return SUCCESS;
		}
	}
	return OPEN; /*presence так и не дождались - похоже на обрыв*/
}


/*--------------------------------------------------*/
/*Посылка байта на указанный порт/пин*/
static ds1820_ret_status ds1820_cmd(volatile uint8_t *__DDRx_p,const uint8_t _MASK, const uint8_t cmd) {

	/*ВНИМАНИЕ! команда 0x44 передается в режиме strong pull up*/
	typedef enum{strong=0,none=!strong} _pullup_t;
	_pullup_t pullup=none;
	uint8_t i;
	int err;

	if (cmd==0x44) pullup=strong;

	_DDRx &= ~_MASK; //Порт на чтение

	for (i=0;i<8;i++) { /*8 бит команды*/

		/*Ждем освобождения линии макс 1 мсек*/
		err=1000;
		while ((_PINx&_MASK)==0) {
			if (err==0) return SHORT; /*Похоже на КЗ*/
			err--;
			_delay_us(1);
		}

		ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { //Запретим прерывания и воостановим по окончании
			if ((cmd&(1<<i))) { /* бит=1  - 4мкс low и 70 мкс high*/
				_PORTx &= ~_MASK; //Сначала порт в 0
				_DDRx |= _MASK; //а затем на вывод
				_delay_us(4);
				_PORTx |= _MASK; //порт в 1
				if (pullup==none) _DDRx &= ~_MASK; //и если не 44 команда - убрать strong pullup
				_delay_us(70);
			}else{	/*бит=0 - 70 мкс low*/
				_PORTx &= ~_MASK;
				_DDRx |= _MASK;
				_delay_us(70);
				_PORTx |= _MASK;
				if (pullup==none) _DDRx &= ~_MASK;
			}
		}
	}
	return SUCCESS;
}


/*--------------------------------------------------*/
/*Чтение байта из указанного порта/пина */
static ds1820_ret_status ds1820_read(volatile uint8_t *__DDRx_p,const uint8_t _MASK, uint8_t *read_byte) {
	uint8_t i;
	int err;

	*read_byte=0;
	_DDRx &= ~_MASK; //Порт на чтение
	for (i=0;i<8;i++) { //Получаем 8 бит

		err=1000;
		while ((_PINx&_MASK)==0) { //Ждем освобождения линии датчиком
			if (err==0) return SHORT;
			err--;
			_delay_us(1);
		}
		ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ /*Требуется точность, поэтому прерывания запрещаем*/
			_PORTx &= ~_MASK; /*строб*/
			_DDRx |= _MASK;
			_delay_us(4);
			_DDRx &= ~_MASK;
			_delay_us(20);
			if ((_PINx&_MASK)) *read_byte|=(1<<i); /*LSB*/
		}
		_delay_us(60);
	}
	return SUCCESS;
}

/*--------------------------------------------------*/
/*Команда на запуск измерения датчику на порту/пине */
ds1820_ret_status ds1820_start(volatile uint8_t *__DDRx_p,uint8_t _MASK){
	ds1820_ret_status st;
	if ((st=ds1820_init(__DDRx_p,_MASK))!=SUCCESS) return st;
	if ((st=ds1820_cmd(__DDRx_p,_MASK,DS1820_SKIP_ROM))!=SUCCESS) return st;
	if ((st=ds1820_cmd(__DDRx_p,_MASK,DS1820_CONVERT_T))!=SUCCESS) return st;
	return SUCCESS;
}

/*--------------------------------------------------*/
/*Получение результата измерения датчика на порту/пине
Температура возвращается "как есть" - удвоенная*/
ds1820_ret_status ds1820_get_temp(volatile uint8_t *__DDRx_p,uint8_t _MASK,int16_t *temp) {
	ds1820_ret_status st;
	uint8_t crc,data=0,i;

	if ((st=ds1820_init(__DDRx_p,_MASK))!=SUCCESS) return st;
	if ((st=ds1820_cmd(__DDRx_p,_MASK,DS1820_SKIP_ROM))!=SUCCESS) return st;
	if ((st=ds1820_cmd(__DDRx_p,_MASK,DS1820_READ_SCRATCHPAD))!=SUCCESS) return st;
	crc=0;

	/*Получаем первый байт - LSB температуры*/
	if ((st=ds1820_read(__DDRx_p,_MASK,&data))!=SUCCESS) return st;
	*temp=data;
	crc=_crc_ibutton_update(crc,data);

	/*Получаем второй байт - MSB температуры*/
	if ((st=ds1820_read(__DDRx_p,_MASK,&data))!=SUCCESS) return st;
	*temp|=(int16_t)(data<<8);
	crc=_crc_ibutton_update(crc,data);

	/*Получим остальные 7 байт для проверки CRC*/
	for (i=0;i<7;i++) {
		if ((st=ds1820_read(__DDRx_p,_MASK,&data))!=SUCCESS) return st;
		crc=_crc_ibutton_update(crc,data);
	}
	if (crc) return CRC;
	if (ds1820_get_type(__DDRx_p,_MASK)==ds18B20) {
		*temp>>=3;
		if (*temp&0x1000) *temp|=0xE000;
	}

	return SUCCESS;
}

ds1820_type ds1820_get_type(volatile uint8_t *__DDRx_p,uint8_t _MASK) {
	uint8_t crc,data=0,i,type;

	if ((ds1820_init(__DDRx_p,_MASK))!=SUCCESS) return NONE;
	if ((ds1820_cmd(__DDRx_p,_MASK,DS1820_READ_ROM))!=SUCCESS) return NONE;
	crc=0;

	/*Получаем первый байт - family код*/
	if ((ds1820_read(__DDRx_p,_MASK,&data))!=SUCCESS) return NONE;
	type=data;
	crc=_crc_ibutton_update(crc,data);

	/*Получим остальные 7 байт для проверки CRC*/
	for (i=0;i<7;i++) {
		if ((ds1820_read(__DDRx_p,_MASK,&data))!=SUCCESS) return NONE;
		crc=_crc_ibutton_update(crc,data);
	}
	if (crc) return NONE;

	if (type==0x10) return ds18S20;
	if (type==0x28) return ds18B20;

	return NONE;
}
